﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Aop.Autofac.Aspects;
using Castle.DynamicProxy;

namespace Aop.Autofac
{
    public class MethodInterceptor : StandardInterceptor
    {
        protected List<OnMethodBondedAspect> OnMethodBondedAspects { get; private set; }
        protected List<KeyValuePair<ParameterInfo, OnMethodArgumentBoundedAspect>> OnMethodArgumetnBoundedAspects { get; private set; }
        protected List<OnMethodArgumentBoundedAspect> OnMethodResultBoundedAspects { get; private set; }

        public MethodInterceptor()
        {
            OnMethodBondedAspects = new List<OnMethodBondedAspect>();
            OnMethodArgumetnBoundedAspects = new List<KeyValuePair<ParameterInfo, OnMethodArgumentBoundedAspect>>();
            OnMethodResultBoundedAspects = new List<OnMethodArgumentBoundedAspect>();
        }

        private new void Intercept(IInvocation invocation)
        {
            Console.WriteLine("类的调用");
        }

        //[DebuggerStepThrough]
        protected override void PerformProceed(IInvocation invocation)
        {
            OnMethodBondedAspects
                .AddRange(GetOnMethodBondedAspects(invocation));

            OnMethodArgumetnBoundedAspects
                .AddRange(GetOnMethodArgumetnBoundedAspects(invocation));

            OnMethodResultBoundedAspects
                .AddRange(GetOnMethodResultBoundedAspects(invocation));

            try
            {
                foreach (var aspect in OnMethodArgumetnBoundedAspects)
                    aspect.Value.CheckArgument(aspect.Key.Name, invocation.Arguments[aspect.Key.Position]);

                foreach (var aspect in OnMethodBondedAspects)
                    aspect.OnBeforeExecuting(invocation);

                var action = CreatAction(OnMethodBondedAspects);
                action.Invoke(invocation);

                foreach (var aspect in OnMethodBondedAspects)
                    aspect.OnExecuted(invocation);

                foreach (var aspect in OnMethodResultBoundedAspects)
                    aspect.CheckArgument("return", invocation.ReturnValue);

            }
            catch (Exception ex)
            {
                foreach (var methodAspect in OnMethodBondedAspects)
                    methodAspect.OnException(invocation, ex);

                if (OnMethodBondedAspects.Any(x => x.IsJideException) || OnMethodBondedAspects.Count == 0)
                    throw;
            }
            finally
            {
                foreach (var methodAspect in OnMethodBondedAspects)
                    methodAspect.OnExit(invocation);
            }

            OnMethodBondedAspects.Clear();
            OnMethodArgumetnBoundedAspects.Clear();
            OnMethodResultBoundedAspects.Clear();
        }

        private IEnumerable<KeyValuePair<ParameterInfo, OnMethodArgumentBoundedAspect>> GetOnMethodArgumetnBoundedAspects(IInvocation invocation)
        {
            var parameters = invocation.Method.GetParameters();
            var result = new List<KeyValuePair<ParameterInfo, OnMethodArgumentBoundedAspect>>(); 

            foreach (var parameter in parameters)
            {
                var aspects = parameter
                    .GetCustomAttributes(typeof (OnMethodArgumentBoundedAspect), true) as OnMethodArgumentBoundedAspect[];

                if (aspects != null)
                    result.AddRange(aspects
                        .Select(aspect => new KeyValuePair<ParameterInfo, OnMethodArgumentBoundedAspect>(parameter, aspect)));
            }

            return result;
        }

        private IEnumerable<OnMethodBondedAspect> GetOnMethodBondedAspects(IInvocation invocation)
        {
            var result = new List<OnMethodBondedAspect>();

            var methodAspects = invocation.Method
                .GetCustomAttributes(typeof (OnMethodBondedAspect), true) as OnMethodBondedAspect[];
            if (methodAspects != null) 
                result.AddRange(methodAspects);

            return result;
        }

        private IEnumerable<OnMethodArgumentBoundedAspect> GetOnMethodResultBoundedAspects(IInvocation invocation)
        {
            var result = new List<OnMethodArgumentBoundedAspect>();

            if (invocation.Method.ReturnParameter == null)
                return result;

            var aspects = invocation.Method.ReturnParameter
                .GetCustomAttributes(typeof(OnMethodArgumentBoundedAspect), true) as OnMethodArgumentBoundedAspect[];

            if (aspects != null)
                result.AddRange(aspects);

            return result;
        }

        private Action<IInvocation> CreatAction(List<OnMethodBondedAspect> aspects, Action<IInvocation> returnAction = null, int i = 0)
        {
            if (aspects == null || aspects.Count == 0)
            {
                returnAction = new Action<IInvocation>((a) =>
                {
                    base.PerformProceed(a);
                });
                return returnAction;
            }

            if (i == aspects.Count) return returnAction;

            var aspect = aspects[i];

            //表示第一次
            if (returnAction == null)
            {
                returnAction = new Action<IInvocation>((a) =>
                {
                    aspect.OnExecuting((q) =>
                    {
                        base.PerformProceed(q);
                    }, a);
                });
            }
            else
            {
                returnAction = new Action<IInvocation>((a) =>
                {
                    aspect.OnExecuting(returnAction, a);
                });
            }
            i++;
            CreatAction(aspects, returnAction, i);
            return returnAction;
        }
    }
}
